#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/spinlock.h>

#include "misc_init.h"

static long misc_device_ioctl(struct file *file, unsigned int cmd, unsigned long user);

DEFINE_SPINLOCK(misc_lock);

misc_ioctl_func_t misc_ioctl_callback = NULL;

static struct file_operations misc_device_fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = misc_device_ioctl,
};

static struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = MISC_DEV_NAME,
    .fops = &misc_device_fops,
};

static long misc_device_ioctl(struct file *file, unsigned int cmd, unsigned long user)
{
    int ret = -1;
    ioctl_data_t user_data = {0};

    spin_lock(&misc_lock);
    if (cmd != MISC_IOCTL_CMD) {
        spin_unlock(&misc_lock);
        return -EINVAL;
    }
    if (copy_from_user(&user_data, (void *)user, sizeof(ioctl_data_t))) {
        spin_unlock(&misc_lock);
        return -EIO;
    }

    misc_ioctl_callback(&user_data);

    ret = copy_to_user((void *)user, &user_data, sizeof(ioctl_data_t));
    if (ret != 0) {
        ret = -EIO;
    }
    spin_unlock(&misc_lock);
    return ret;
}

int misc_device_init(misc_ioctl_func_t callback)
{
    int ret = RET_ERROR;

    if (callback == NULL) {
        return RET_ERROR;
    }

    misc_ioctl_callback = callback;
    ret = misc_register(&misc_dev);
    if (ret) {
        printk(KERN_ERR "Failed to register misc device\n");
        return RET_ERROR;
    }
    return RET_SUCCESS;
}

void misc_device_exit(void)
{
    misc_ioctl_callback = NULL;
    misc_deregister(&misc_dev);
    printk(KERN_INFO "Goodbye from misc_device!\n");
}
